feat(documents): DELETE /api/documents/{id} with graph-cascade cleanup#105
Open
feat(documents): DELETE /api/documents/{id} with graph-cascade cleanup#105
Conversation
…ict CSP The strict CSP shipped from internal/api/security_headers.go uses script-src 'self' 'wasm-unsafe-eval' — no 'unsafe-inline'. The theme-flash FOUC guard in index.html was an inline <script>, so every page load logged a CSP violation: Refused to execute inline script because it violates the following Content Security Policy directive: script-src 'self' 'wasm-unsafe-eval' The script never ran, which meant a brief flash-of-wrong-theme on first paint in dark mode (and vice-versa). Move the script verbatim into ui/public/theme-flash.js (Vite copies public/ to /dist root at build time) and reference it from index.html via <script src="/theme-flash.js">. CSP 'self' allows it without an inline-script exception. The script body is unchanged so behaviour is identical when it runs. Trade-off: one extra HTTP/2 request before paint. The file is 1.2 KiB so this is well below the perceptible-jank threshold and worth it for keeping the strict CSP intact.
Adds an authenticated REST DELETE endpoint plus per-row and
header-level UI controls so users can remove a bad upload and have
the knowledge graph clean up entities, relationships, claims, chunks,
and embeddings sourced from that doc — without leaving orphan rows or
hitting the documents.file_hash UNIQUE collision on the next index.
Backend:
- internal/store/store.go: rewrite Store.DeleteDocument as a single
transaction that deletes relationships/claims by doc_id, chunks
(embeddings cascade via FK), the document row, then sweeps orphan
entities. Communities are deliberately left stale for finalize.
- internal/api/handlers.go: new deleteDocument handler returning 204
on success, 404 on unknown id, 500 on tx failure. Invalidates the
per-project HNSW index so the next search rebuilds.
- internal/api/router.go: register DELETE /api/documents/{id} under
the existing bearer-auth + project chain.
- internal/api/document_delete_test.go: handler tests (404, 204 happy
path with full graph-cleanup assertions, idempotency) plus a store-
layer cascade test.
Frontend:
- ui/src/hooks/api/useDocs.ts: useDeleteDoc mutation that invalidates
docs/stats/entityGraph queries on success.
- ui/src/routes/documents/DeleteDocumentDialog.tsx: shared confirm
dialog with destructive button and sonner toast.
- DocumentsList: per-row Trash2 ghost icon button.
- DocumentView: destructive header button that navigates back on
success.
- Providers: mount the Toaster portal.
- Vitest unit test for the dialog covering render, success path, and
server-error path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
| Delete | ||
| </Button> | ||
| </header> | ||
| {data && id && ( |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Today there is no way to remove a document once it has been uploaded. Users hit the
documents.file_hash UNIQUEcollision when they try to reindex (the supersede path bumpsversionbut never frees the row), and a bad upload leaves orphan entities, relationships, claims, chunks, and embeddings in the knowledge graph forever.This PR adds an authenticated
DELETE /api/documents/{id}plus a per-row icon button on the documents list and a destructive button on the document detail page. The cascade runs in a single SQLite transaction and is idempotent: redeleting the same id is a 404, not a second 204.Cascade order (single tx):
relationshipsWHEREdoc_id = ?— drop edges sourced from this docclaimsWHEREdoc_id = ?— drop claims sourced from this docchunksWHEREdoc_id = ?— embeddings cascade via FKdocumentsWHEREid = ?— the row itselfcommunity_membersrows cascade via FKTradeoff: communities are deliberately NOT recomputed inline. Louvain on a meaningfully sized graph is too slow for an interactive DELETE; stale community titles/summaries are tolerable until the next manual
community.finalize. Documented in both the handler and the store-layer comments.The per-project HNSW index is invalidated so the next vector search rebuilds against the post-delete chunk set.
Files changed
Backend (Go)
internal/api/router.go— registerDELETE /api/documents/{id}in the existing bearer-auth chaininternal/api/handlers.go— newdeleteDocumenthandler (204 / 404 / 500)internal/store/store.go— rewriteStore.DeleteDocumentwith the transactional cascade + orphan sweepinternal/api/document_delete_test.go— handler table tests + store-layer cascade testFrontend (TS/React)
ui/src/hooks/api/useDocs.ts—useDeleteDocmutation, invalidatesdocs,stats,entityGraphui/src/routes/documents/DeleteDocumentDialog.tsx— shared confirm dialog with destructive button + sonner toastui/src/routes/documents/DocumentsList.tsx— per-rowTrash2ghost icon buttonui/src/routes/documents/DocumentView.tsx— destructive header button, navigates to/docson successui/src/components/layout/Providers.tsx— mount the<Toaster />portal (was unmounted; no toasts existed in the codebase yet)ui/src/routes/documents/__tests__/DeleteDocumentDialog.test.tsx— Vitest + MSW unit test (render, success, server-error)Test plan
make checkclean (Go build + vet + test, UI build + vitest run): 31 UI test files / 105 tests pass; Go suite greenDELETE /api/documents/:idand fires success toast +onDeletedcallback; server 500 surfaces error toast and dialog stays open./docsiqbinary: seeded a doc with 2 entities + 1 relationship + 1 claim,DELETE /api/documents/{id}returned 204 with empty body,GET /api/documentsreturnednull,GET /api/graphreturned{edges:[], nodes:[]},GET /api/entitiesreturnednull(orphan sweep landed). Second DELETE returned 404. Unauthenticated DELETE returned 401.Out of scope
🤖 Generated with Claude Code